import "getusermedia-polyfill";

let mediaStream: MediaStream | null = null;

export const screenWidth =
  window.innerWidth ||
  document.documentElement.clientWidth ||
  document.body.clientWidth ||
  window.screen.width ||
  document.body.scrollWidth;

export const screenHeight =
  window.innerHeight ||
  document.documentElement.clientHeight ||
  document.body.clientHeight ||
  window.screen.height ||
  document.body.scrollHeight;

export const getMediaError = (error: any) => {
  if (!error) {
    return {
      name: "",
      message: "",
    };
  }
  if (error.name) {
    switch (error.name) {
      case "NotFoundError":
        return {
          name: error.name,
          message: "未找到指定的媒体设备或无法访问。",
        };
      case "PermissionDeniedError":
        return {
          name: error.name,
          message: "用户拒绝访问媒体设备。",
        };
      case "NotAllowedError":
        return {
          name: error.name,
          message: "自动播放策略不允许在没有用户交互的情况下启动媒体播放。",
        };
      case "SecurityError":
        return {
          name: error.name,
          message: "在获取媒体设备时发生了安全错误。",
        };
      case "OverconstrainedError":
        return {
          name: error.name,
          message: "无法满足指定的媒体约束。",
        };
      case "MediaStreamError":
        return {
          name: error.name,
          message: "在获取媒体视频流时发生了错误。",
        };
      case "GetUserMediaError":
        return {
          name: error.name,
          message: "浏览器不支持 getUserMedia API",
        };
      case "UnknownError":
        return {
          name: error.name,
          message: "未知错误。",
        };
      default:
        return {
          name: error.name,
          message: `发生了一个错误: ${error.message}`,
        };
    }
  } else {
    return {
      name: "OtherError",
      message: error.message || "其他错误。",
    };
  }
};

export const getUserMedia = (
  options: MediaTrackConstraints = {},
): Promise<{ success: boolean; name: string; message: string }> => {
  return new Promise((resolve) => {
    if (supportUserMedia()) {
      // 停止当前视频流
      stopMediaStream();
      navigator.mediaDevices
        .getUserMedia({
          audio: false,
          video: options,
        })
        .then((stream) => {
          if (stream) {
            mediaStream = stream; // 统一管理stream流数据 避免摄像头没法关闭
            resolve({
              success: true,
              name: "",
              message: "",
            });
          } else {
            resolve({
              success: false,
              name: "MediaStreamError",
              message: "在获取媒体视频流时发生了错误。",
            });
          }
        })
        .catch((e) => {
          console.warn(e);
          const err = getMediaError(e);
          resolve({
            success: false,
            name: err.name,
            message: err.message,
          });
        });
    } else {
      resolve({
        success: false,
        name: "GetUserMediaError",
        message: "浏览器不支持 getUserMedia API",
      });
    }
  });
};

export const supportUserMedia = () => {
  return !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia);
};

export const getMediaStream = () => {
  if (mediaStream) return mediaStream;
  return null;
};

export const stopMediaStream = () => {
  if (mediaStream) {
    mediaStream.getTracks().forEach((track) => {
      track.stop();
    });
    mediaStream = null;
  }
};
export class Camera {
  options: MediaTrackConstraints;
  video: HTMLVideoElement | null;
  canvas: HTMLCanvasElement | null;
  ratio: number;
  frameRate: number;
  track: {};
  enumerateDevices: MediaDeviceInfo[];
  constructor(
    options?: MediaTrackConstraints,
    video?: HTMLVideoElement | null,
    canvas?: HTMLCanvasElement | null,
  ) {
    this.ratio = window.devicePixelRatio || 1;
    this.frameRate = 0;
    this.track = {};
    this.enumerateDevices = [];
    this.video = video || null;
    this.canvas = canvas || null;
    this.options = Object.assign(
      {
        facingMode: "environment",
        width: screenWidth * this.ratio,
        height: screenHeight * this.ratio,
      },
      options,
    );
  }
  async getEnumerateDevices(
    callback?: (res: MediaDeviceInfo[]) => void,
  ): Promise<MediaDeviceInfo[]> {
    return new Promise((resolve) => {
      navigator.mediaDevices
        .enumerateDevices()
        .then((devices) => {
          const enumerateDevices: MediaDeviceInfo[] = [];
          devices.forEach((mediaDevice) => {
            if (mediaDevice.kind === "videoinput") {
              enumerateDevices.push(mediaDevice);
            }
          });
          callback && callback(enumerateDevices);
          resolve(enumerateDevices);
        })
        .catch((e) => {
          console.warn(e);
          callback && callback([]);
          resolve([]);
        });
    });
  }
  async getTrack() {
    if (mediaStream) {
      const track: any = mediaStream.getVideoTracks()[0];
      if (track) {
        return {
          id: track.id || "",
          deviceId: track.deviceId || "",
          groupId: track.groupId || "",
          kind: track.kind || "",
          label: track.label || "",
        };
      }
    }
    return {};
  }
  async getFrameRate() {
    if (mediaStream) {
      const track = mediaStream.getVideoTracks()[0];
      if (track) {
        return track.getSettings().frameRate || 0;
      }
    }
    return 0;
  }
  async start(
    callback?: (res: {
      success: boolean;
      name: string;
      message: string;
    }) => void,
  ): Promise<{ success: boolean; name: string; message: string }> {
    await this.stop();
    return new Promise((resolve) => {
      const resolveFun = (res: {
        success: boolean;
        name: string;
        message: string;
      }) => {
        callback && callback(res);
        resolve(res);
      };
      const { width, height, facingMode, ...rest } = this.options as any;
      getUserMedia({
        width: { ideal: height },
        height: { ideal: width },
        facingMode,
        ...rest,
      }).then(
        async (res: { success: boolean; name: string; message: string }) => {
          if (res.success) {
            if (mediaStream) {
              // 设置分辨率
              const track = mediaStream.getVideoTracks()[0];
              if (track.applyConstraints) {
                await track.applyConstraints({
                  width: { ideal: height },
                  height: { ideal: width },
                });
              }

              // 当前帧率
              this.frameRate = await this.getFrameRate();

              // 当前摄像头信息
              this.track = await this.getTrack();

              // 所有摄像头信息
              this.enumerateDevices = await this.getEnumerateDevices();

              // 设置 video
              if (this.video) {
                this.video.style.width = "100%";
                this.video.style.height = "100%";
                this.video.style.objectFit = "cover";
                if (this.options.facingMode == "user") {
                  this.video.style.transform = "scale(-1,1)";
                } else {
                  this.video.style.transform = "";
                }
                this.video.setAttribute("autoplay", "true");
                this.video.setAttribute("muted", "true");
                this.video.setAttribute("playsinline", "true");
                this.video.setAttribute("mtt-playsinline", "true");
                this.video.setAttribute("webkit-playsinline", "true");
                this.video.setAttribute("x5-playsinline", "true");
                this.video.setAttribute("x5-video-player-type", "h5");
                this.video.setAttribute("x5-video-orientation", "portrait");
                this.video.setAttribute(
                  "poster",
                  "",
                );
                this.video.autoplay = true;
                this.video.muted = true;
                this.video.playbackRate = 1.0;

                if ("srcObject" in this.video) {
                  this.video.srcObject = mediaStream;
                } else {
                  // 如果浏览器不支持srcObject属性（例如一些旧版浏览器），则使用createObjectURL方法
                  (this.video as HTMLVideoElement).src = URL.createObjectURL(
                    mediaStream as unknown as Blob,
                  );
                }
                this.video.onloadedmetadata = () => {
                  if (this.video) {
                    this.video.width = width;
                    this.video.height = height;

                    if (this.canvas) {
                      this.canvas.width = this.video.width;
                      this.canvas.height = this.video.height;
                    }

                    this.video
                      .play()
                      .then(() =>
                        resolveFun({
                          success: true,
                          name: "",
                          message: "",
                        }),
                      )
                      .catch(() => {
                        resolveFun({
                          success: false,
                          name: "NotAllowedError",
                          message:
                            "自动播放策略不允许在没有用户交互的情况下启动媒体播放。",
                        });
                      });
                    this.video.onloadedmetadata = null;
                  }
                };
              } else {
                resolveFun({
                  success: true,
                  name: "",
                  message: "",
                });
              }
            } else {
              resolveFun({
                success: false,
                name: "MediaStreamError",
                message: "在获取媒体视频流时发生了错误。",
              });
            }
          } else {
            resolveFun(res);
          }
        },
      );
    });
  }
  async stop() {
    if (this.video) {
      if ("srcObject" in this.video) {
        const stream = this.video.srcObject as any;
        if (stream && stream.getTracks) {
          const tracks = stream.getTracks();
          tracks.forEach((track: MediaStreamTrack) => {
            track.stop();
          });
        }
        this.video.srcObject = null;
      } else {
        const stream = (this.video as HTMLVideoElement).src as any;
        if (stream && stream.getTracks) {
          const tracks = stream.getTracks();
          tracks.forEach((track: MediaStreamTrack) => {
            track.stop();
          });
        }
        URL.revokeObjectURL((this.video as HTMLVideoElement).src);
        (this.video as HTMLVideoElement).src = "";
      }
    }
    stopMediaStream();
  }
  async switch() {
    const facingMode = this.options.facingMode == "user";
    this.options.facingMode = facingMode ? "environment" : "user";
    return this.start();
  }
  async snap({
    type = "image/png",
    quality,
    callback,
  }: {
    type?: string;
    quality?: number;
    callback?: (base: string) => void;
  } = {}): Promise<string> {
    return new Promise((resolve) => {
      let dataURL = "";
      if (this.video) {
        if (!this.canvas) {
          this.canvas = document.createElement("canvas");
        }

        const ctx = this.canvas.getContext("2d");
        const width = this.video.width;
        const height = this.video.height;

        this.canvas.width = width;
        this.canvas.height = height;

        if (ctx) {
          ctx.drawImage(this.video, 0, 0, width, height);
          dataURL = this.canvas.toDataURL(type, quality) || "";
        }
      }
      callback && callback(dataURL);
      resolve(dataURL);
    });
  }
  async destory() {
    await this.stop();
    this.video = null;
    this.canvas = null;
    this.frameRate = 0;
    this.track = {};
    this.enumerateDevices = [];
  }
}

export default Camera;
